package wc.model.grid;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import wc.model.component.Component;

public class TileGrid<T extends Component> implements Iterable<Component> {
	private int width;
	private int height;

	private Component[][] tiles;
	private Map<T, int[]> originalLocations;

	public TileGrid(int width, int height) {
		this.width = width;
		this.height = height;
		tiles = new Component[width][height];
		originalLocations = new HashMap<T, int[]>();
		
		// Set all the tiles to null so we can do a check for it later
		for(int x = 0; x < width; x++) {
			for(int y = 0; y < height; y++) {
				tiles[x][y] = null;
			}
		}
	}

	// In the first loop we go through the locations of the component we want to set and check
	// if it intersects with any component already placed on the grid. It also checks if all the
	// tiles are on the grid and don't fall outside the boundries. If this check passes it will place
	// the components in the second lop
	public boolean addComponent(int x, int y, T component) {
		TileGridIterator it = (TileGridIterator) component.getRelativeLocations().iterator();
		while(it.hasNext()) {
			if(it.next() != null) {
				if(!isValidLocation(x+it.getX(), y+it.getY()) || tiles[x+it.getX()][y+it.getY()] != null) {
					return false;
				}
			}
		}
		it = (TileGridIterator) component.getRelativeLocations().iterator();
		while(it.hasNext()) {
			Component c = it.next();
			if(c != null) {
				tiles[x+it.getX()][y+it.getY()] = component;
			}
		}
		
		// These coordinates are for the left top corner, those are needed to determe the placement of
		// the store components in the room.
		int[] location = new int[2];
		location[0] = x;
		location[1] = y;
		originalLocations.put(component, location);
		return true;
	}


	//Only to be called by factories!
	public void setComponent(int x, int y, T component) {
		if(isValidLocation(x,y))
			tiles[x][y] = component;
	}
	
	//The relative locations TileGrid of MallRooms have the entire grid filled with the MallRoom in question 
	public void setComponent(T component) {
		for(int x = 0; x < width; x++) {
			for(int y = 0; y < height; y++) {
				tiles[x][y] = component;
			}
		}
	}

	public TileGrid<T> removeComponent(T component) {
		TileGrid<T> removedComponent = new TileGrid<T>(width, height);
		TileGridIterator it = (TileGridIterator) iterator();
		while(it.hasNext()) {
			Component c = it.next();
			if(c == component) {
				removedComponent.tiles[it.x][it.y]= c; 
				tiles[it.x][it.y] = null; 
			}
		}
		return removedComponent;
	}
	
	public Component getComponent(int x, int y) {
		return tiles[x][y];
	}

	public int getWidth() {
		return width;
	}

	public int getHeight() {
		return height;
	}

	public Component[][] getTiles() {
		return tiles;
	}

	private boolean isValidLocation(int x, int y) {
		return (x >= 0 && x < getWidth() && y >= 0 && y < getHeight());
	}
	
	public int[] getOriginalLocationByComponent(T component) {
		return originalLocations.get(component);
	}

	@Override
	public Iterator<Component> iterator() {
		return new TileGridIterator();
	}

	//Clockwise!!!
	//Do not call while iterating!!!
	//Not made for rotating floors, stores, etc. Only for relativeLocations of components that can't contain other components
	public void rotateRelativeLocations90() {
		Component[][] rotatedTiles = new Component[height][width]; //<-- notice the inversion of width and height
		for (int x = 0; x < width; x++) {
			for (int y = 0; y < height; y++) {
				rotatedTiles[height-1-y][x] = tiles[x][y];
			}
		}
		tiles = rotatedTiles;
	}

	public class TileGridIterator implements Iterator<Component> {
		private int x;
		private int y;

		public TileGridIterator() {
			x = 0;
			y = 0;
		}

		@Override
		public boolean hasNext() {
			return x < getWidth() && y < getHeight();
		}

		@Override
		public Component next() {
			if(hasNext()) {
				Component value = tiles[x][y];

				x++;
				if(x == getWidth()) {
					x = 0;
					y++;
				}

				return value;
			}

			return null;
		}

		//Returns X and Y for the components that have already been retrieved
		//with next(), therefore logic is required to retrieve that component's location 
		public int getX() {
			if(x == 0 && y > 0)
				return width-1;
			return (x-1);
		}
		public int getY() {
			if(x == 0)
				return (y-1);
			return y;
		}

		@Override
		public void remove() {
			throw new UnsupportedOperationException();
		}
	}
}
